In this example, we are going to apply a CNN to classify dogs vs. cats images. This will walk you through the fundamentals of importing images, applying image augmentation, and performing classification on them.

Learning objectives:

Part 1: Data Preparation

Image location

We are going to use the Dogs vs. Cats Kaggle competition data set (https://www.kaggle.com/c/dogs-vs-cats/data). However, do to size and runtime limitations, we are going to only use a subset of the data. We have already set up the directories which look like:

- data
   └── dogs-vs-cats
       └── train
           └── cats
               ├── cat.1.jpg
               ├── cat.2.jpg
               └── ...
           └── dogs
               ├── dog.1.jpg
               ├── dog.2.jpg
               └── ...
       └── validation
           ├── cats
           └── dogs
       └── test
           ├── cats
           └── dogs
# define the directories:
image_dir <- here::here("docs", "data", "dogs-vs-cats")
train_dir <- file.path(image_dir, "train")
valid_dir <- file.path(image_dir, "validation")
test_dir <- file.path(image_dir, "test")

# create train, validation, and test file paths for cat images
train_cats_dir <- file.path(train_dir, "cats")
valid_cats_dir <- file.path(valid_dir, "cats")
test_cats_dir <- file.path(test_dir, "cats")

# create train, validation, and test file paths for cat images
train_dogs_dir <- file.path(train_dir, "dogs")
valid_dogs_dir <- file.path(valid_dir, "dogs")
test_dogs_dir <- file.path(test_dir, "dogs")

Data set

Although there are 25,000 images in this data set, we are going to use a very small subset, which includes:

cat("Cat images:", "\n")
Cat images: 
cat(" - total training cat images:", length(list.files(train_cats_dir)), "\n")
 - total training cat images: 1000 
cat(" - total validation cat images:", length(list.files(valid_cats_dir)), "\n")
 - total validation cat images: 500 
cat(" - total test cat images:", length(list.files(test_cats_dir)), "\n\n")
 - total test cat images: 500 
cat("Dog images:", "\n")
Dog images: 
cat(" - total training dog images:", length(list.files(train_dogs_dir)), "\n")
 - total training dog images: 1000 
cat(" - total validation dog images:", length(list.files(valid_dogs_dir)), "\n")
 - total validation dog images: 500 
cat(" - total test dog images:", length(list.files(test_dogs_dir)), "\n")
 - total test dog images: 500 

Let’s check out the first 10 cat and dog images:

op <- par(mfrow = c(4, 5), pty = "s", mar = c(0.1, 0.1, 0.1, 0.1))
for (i in 1:10) {
  plot(as.raster(jpeg::readJPEG(paste0(train_cats_dir, "/cat.", i, ".jpg"))))
  plot(as.raster(jpeg::readJPEG(paste0(train_dogs_dir, "/dog.", i, ".jpg"))))
}
par(op)

Part 2: Model 1

Define and compile model

We’re going to set up a simple CNN model that contains steps you saw in the previous module. This CNN includes:

  • Four sequential conv and max pooling layers
  • Flatten layer
  • Densly-connected network
  • Single binary output
model <- keras_model_sequential() %>%
  layer_conv_2d(filters = 32, kernel_size = c(3, 3), activation = "relu", 
                input_shape = c(150, 150, 3)) %>%
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  
  layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = "relu") %>% 
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  
  layer_conv_2d(filters = 128, kernel_size = c(3, 3), activation = "relu") %>% 
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  
  layer_conv_2d(filters = 128, kernel_size = c(3, 3), activation = "relu") %>% 
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  
  layer_flatten() %>%
  
  layer_dense(units = 512, activation = "relu") %>%
  layer_dense(units = 1, activation = "sigmoid")

summary(model)
Model: "sequential_1"
______________________________________________________________________________
Layer (type)                       Output Shape                   Param #     
==============================================================================
conv2d (Conv2D)                    (None, 148, 148, 32)           896         
______________________________________________________________________________
max_pooling2d (MaxPooling2D)       (None, 74, 74, 32)             0           
______________________________________________________________________________
conv2d_1 (Conv2D)                  (None, 72, 72, 64)             18496       
______________________________________________________________________________
max_pooling2d_1 (MaxPooling2D)     (None, 36, 36, 64)             0           
______________________________________________________________________________
conv2d_2 (Conv2D)                  (None, 34, 34, 128)            73856       
______________________________________________________________________________
max_pooling2d_2 (MaxPooling2D)     (None, 17, 17, 128)            0           
______________________________________________________________________________
conv2d_3 (Conv2D)                  (None, 15, 15, 128)            147584      
______________________________________________________________________________
max_pooling2d_3 (MaxPooling2D)     (None, 7, 7, 128)              0           
______________________________________________________________________________
flatten (Flatten)                  (None, 6272)                   0           
______________________________________________________________________________
dense_2 (Dense)                    (None, 512)                    3211776     
______________________________________________________________________________
dense_3 (Dense)                    (None, 1)                      513         
==============================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0
______________________________________________________________________________

Compile the model:

model %>% compile(
  loss = "binary_crossentropy",
  optimizer = optimizer_rmsprop(lr = 1e-4),
  metrics = "accuracy"
)

Read images from directories

image_data_generator will:

  1. Read the image files
  2. Decode the image to RGB grids of pixels
  3. Convert these into floating point tensors
  4. Rescale pixel values to [0, 1] interval

image_data_generator provides other capabilities that we’ll look at shortly.

flow_images_from_directory will:

  1. Apply image_data_generator
  2. To a batch of 20 images at a time
  3. From our training directory (randomly shuffling btwn subdirectories)
  4. Resize these images to be consistent size of 150x150 pixels
  5. Apply binary labels
train_datagen <- image_data_generator(rescale = 1/255)
valid_datagen <- image_data_generator(rescale = 1/255)

train_generator <- flow_images_from_directory(
  train_dir,
  train_datagen,
  target_size = c(150, 150),
  batch_size = 20,
  class_mode = "binary"
)

validation_generator <- flow_images_from_directory(
  valid_dir,
  valid_datagen,
  target_size = c(150, 150),
  batch_size = 20,
  class_mode = "binary",
  seed = 123L
)

If we get the first batch from the generator, you will see that it yields 20 images of 150x150 pixels with three channels (20, 150, 150, 3) along with their binary labels (0, 1).

batch <- generator_next(train_generator)
str(batch)
List of 2
 $ : num [1:20, 1:150, 1:150, 1:3] 0.161 0.122 0.486 0.482 0.882 ...
 $ : num [1:20(1d)] 1 0 1 1 0 1 1 0 0 1 ...

Train the model

To train our model we’ll use fit_generator which is the equivalent of fit for data generators. We provide it our generators for the training and validation data. Plus, we need to specify:

  • steps_per_epoch: how many samples to draw from the training generator before declaring an epoch over. Our generator supplies batches of 20 and we have 2,000 training images so we need 100 steps.
  • validation_steps: how many samples to draw from the validation generator. Our generator supplies batches of 20 and we have 1,000 validation images so we need 50 steps.

Without a GPU this will take approximately 20 minutes to train

history <- model %>% fit_generator(
  train_generator,
  steps_per_epoch = 100,
  epochs = 30,
  validation_data = validation_generator,
  validation_steps = 50
)

View history

plot(history)

Save the model

model %>% save_model_hdf5("cats_and_dogs_small_1.h5")

Part 3: Model 2 with image augmentation

Image Augmentation

Our model above does ok but definitely has room for improvement. One approach to improve performance is to collect more data. Unfortunately, this is not always an option. An alternative is to use data augmentation.

datagen <- image_data_generator(
  rescale = 1/255,
  rotation_range = 40,
  width_shift_range = 0.2,
  height_shift_range = 0.2,
  shear_range = 0.2,
  zoom_range = 0.2,
  horizontal_flip = TRUE,
  fill_mode = "nearest"
)

The following helps to visualize the idea of image augmentation by:

  • Reading in the first image and resizing it to 150x150,
  • Converting it to an array with shape (150, 150, 3),
  • Reshaping it to (1, 150, 150, 3),
  • Generating batches of randomly transformed images.
fnames <- list.files(train_cats_dir, full.names = TRUE)
img_path <- fnames[[1]]

img <- image_load(img_path, target_size = c(150, 150))
img_array <- image_to_array(img)
img_array <- array_reshape(img_array, c(1, 150, 150, 3))

augmentation_generator <- flow_images_from_data(
  img_array,
  generator = datagen,
  batch_size = 1
)
WARNING: Logging before flag parsing goes to stderr.
W1019 11:38:47.535993 140736703529920 lazy_loader.py:50] 
The TensorFlow contrib module will not be included in TensorFlow 2.0.
For more information, please see:
  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md
  * https://github.com/tensorflow/addons
  * https://github.com/tensorflow/io (for I/O related ops)
If you depend on functionality not listed there, please file an issue.
op <- par(mfrow = c(2, 5), pty = "s", mar = c(0, 0.1, 0, 0.1))
for (i in 1:10) {
  batch <- generator_next(augmentation_generator)
  plot(as.raster(batch[1,,,]))
}
par(op)

Model 2

Let’s create a new model that includes image augmentation and we’ll apply the dropout regularization method. The following creates a CNN architecture with:

  • Four sequential conv and max pooling layers
  • Flatten layer
  • Dropout layer (new)
  • Densly-connected network
  • Single binary output
model <- keras_model_sequential() %>%
  layer_conv_2d(filters = 32, kernel_size = c(3, 3), activation = "relu", input_shape = c(150, 150, 3)) %>%
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = "relu") %>% 
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  layer_conv_2d(filters = 128, kernel_size = c(3, 3), activation = "relu") %>% 
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  layer_conv_2d(filters = 128, kernel_size = c(3, 3), activation = "relu") %>% 
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  layer_flatten() %>%
  layer_dropout(rate = 0.5) %>%
  layer_dense(units = 512, activation = "relu") %>%
  layer_dense(units = 1, activation = "sigmoid")

model %>% compile(
  loss = "binary_crossentropy",
  optimizer = optimizer_rmsprop(lr = 1e-4),
  metrics = "accuracy"
)

Now we can add image augmentation to our image_data_generator(). The rest of the inputs remain the same; however, we use larger batch size (32) and we do not need to worry about batch_size x steps_per_epoch equaling the number of training images since we are doing image augmentation.

# only augment training data
train_datagen <- image_data_generator(
  rescale = 1/255,
  rotation_range = 40,
  width_shift_range = 0.2,
  height_shift_range = 0.2,
  shear_range = 0.2,
  zoom_range = 0.2,
  horizontal_flip = TRUE,
)

# do not augment test and validation data
test_datagen <- image_data_generator(rescale = 1/255)

# generate batches of data from training directory
train_generator <- flow_images_from_directory(
  train_dir,
  train_datagen,
  target_size = c(150, 150),
  batch_size = 32,
  class_mode = "binary"
)

# generate batches of data from validation directory
validation_generator <- flow_images_from_directory(
  valid_dir,
  test_datagen,
  target_size = c(150, 150),
  batch_size = 32,
  class_mode = "binary"
)

# train model
history <- model %>%
  fit_generator(
    train_generator,
    steps_per_epoch = 100,
    epochs = 100,
    validation_data = validation_generator,
    validation_steps = 50
  )
model %>% save_model_hdf5("cats_and_dogs_small_2.h5")
LS0tCnRpdGxlOiAiQ29tcHV0ZXIgdmlzaW9uICYgQ05OczogQ2F0cyB2cy4gRG9ncyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgY2FjaGUgPSBUUlVFKQoKIyBJbml0aWFsaXplIHBhY2thZ2UKbGlicmFyeShrZXJhcykKYGBgCgpJbiB0aGlzIGV4YW1wbGUsIHdlIGFyZSBnb2luZyB0byBhcHBseSBhIENOTiB0byBjbGFzc2lmeSBkb2dzIHZzLiBjYXRzIGltYWdlcy4gClRoaXMgd2lsbCB3YWxrIHlvdSB0aHJvdWdoIHRoZSBmdW5kYW1lbnRhbHMgb2YgaW1wb3J0aW5nIGltYWdlcywgYXBwbHlpbmcgaW1hZ2UgCmF1Z21lbnRhdGlvbiwgYW5kIHBlcmZvcm1pbmcgY2xhc3NpZmljYXRpb24gb24gdGhlbS4KCkxlYXJuaW5nIG9iamVjdGl2ZXM6CgotIFdoYXQgaW1hZ2UgZ2VuZXJhdG9ycyBhcmUsIHdoeSBhbmQgaG93IHRvIHVzZSB0aGVtLgotIFdoYXQgaW1hZ2UgYXVnbWVudGF0aW9uIGlzLCB3aHkgYW5kIGhvdyB0byB1c2UgdGhlbS4KCiMgUGFydCAxOiBEYXRhIFByZXBhcmF0aW9uCgojIyBJbWFnZSBsb2NhdGlvbgoKV2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgRG9ncyB2cy4gQ2F0cyBLYWdnbGUgY29tcGV0aXRpb24gZGF0YSBzZXQKKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYy9kb2dzLXZzLWNhdHMvZGF0YSkuIEhvd2V2ZXIsIGRvIHRvIHNpemUgYW5kIHJ1bnRpbWUgCmxpbWl0YXRpb25zLCB3ZSBhcmUgZ29pbmcgdG8gb25seSB1c2UgYSBzdWJzZXQgb2YgdGhlIGRhdGEuICBXZSBoYXZlIGFscmVhZHkgCnNldCB1cCB0aGUgZGlyZWN0b3JpZXMgd2hpY2ggbG9vayBsaWtlOgoKYGBgCi0gZGF0YQogICDilJTilIDilIAgZG9ncy12cy1jYXRzCiAgICAgICDilJTilIDilIAgdHJhaW4KICAgICAgICAgICDilJTilIDilIAgY2F0cwogICAgICAgICAgICAgICDilJzilIDilIAgY2F0LjEuanBnCiAgICAgICAgICAgICAgIOKUnOKUgOKUgCBjYXQuMi5qcGcKICAgICAgICAgICAgICAg4pSU4pSA4pSAIC4uLgogICAgICAgICAgIOKUlOKUgOKUgCBkb2dzCiAgICAgICAgICAgICAgIOKUnOKUgOKUgCBkb2cuMS5qcGcKICAgICAgICAgICAgICAg4pSc4pSA4pSAIGRvZy4yLmpwZwogICAgICAgICAgICAgICDilJTilIDilIAgLi4uCiAgICAgICDilJTilIDilIAgdmFsaWRhdGlvbgogICAgICAgICAgIOKUnOKUgOKUgCBjYXRzCiAgICAgICAgICAg4pSU4pSA4pSAIGRvZ3MKICAgICAgIOKUlOKUgOKUgCB0ZXN0CiAgICAgICAgICAg4pSc4pSA4pSAIGNhdHMKICAgICAgICAgICDilJTilIDilIAgZG9ncwpgYGAKCmBgYHtyIGltYWdlLWZpbGUtcGF0aHN9CiMgZGVmaW5lIHRoZSBkaXJlY3RvcmllczoKaW1hZ2VfZGlyIDwtIGhlcmU6OmhlcmUoImRvY3MiLCAiZGF0YSIsICJkb2dzLXZzLWNhdHMiKQp0cmFpbl9kaXIgPC0gZmlsZS5wYXRoKGltYWdlX2RpciwgInRyYWluIikKdmFsaWRfZGlyIDwtIGZpbGUucGF0aChpbWFnZV9kaXIsICJ2YWxpZGF0aW9uIikKdGVzdF9kaXIgPC0gZmlsZS5wYXRoKGltYWdlX2RpciwgInRlc3QiKQoKIyBjcmVhdGUgdHJhaW4sIHZhbGlkYXRpb24sIGFuZCB0ZXN0IGZpbGUgcGF0aHMgZm9yIGNhdCBpbWFnZXMKdHJhaW5fY2F0c19kaXIgPC0gZmlsZS5wYXRoKHRyYWluX2RpciwgImNhdHMiKQp2YWxpZF9jYXRzX2RpciA8LSBmaWxlLnBhdGgodmFsaWRfZGlyLCAiY2F0cyIpCnRlc3RfY2F0c19kaXIgPC0gZmlsZS5wYXRoKHRlc3RfZGlyLCAiY2F0cyIpCgojIGNyZWF0ZSB0cmFpbiwgdmFsaWRhdGlvbiwgYW5kIHRlc3QgZmlsZSBwYXRocyBmb3IgY2F0IGltYWdlcwp0cmFpbl9kb2dzX2RpciA8LSBmaWxlLnBhdGgodHJhaW5fZGlyLCAiZG9ncyIpCnZhbGlkX2RvZ3NfZGlyIDwtIGZpbGUucGF0aCh2YWxpZF9kaXIsICJkb2dzIikKdGVzdF9kb2dzX2RpciA8LSBmaWxlLnBhdGgodGVzdF9kaXIsICJkb2dzIikKYGBgCgojIyBEYXRhIHNldAoKQWx0aG91Z2ggdGhlcmUgYXJlIDI1LDAwMCBpbWFnZXMgaW4gdGhpcyBkYXRhIHNldCwgd2UgYXJlIGdvaW5nIHRvIHVzZSBhIHZlcnkgCnNtYWxsIHN1YnNldCwgd2hpY2ggaW5jbHVkZXM6CgpgYGB7ciB2ZXJpZnktZGF0YX0KY2F0KCJDYXQgaW1hZ2VzOiIsICJcbiIpCmNhdCgiIC0gdG90YWwgdHJhaW5pbmcgY2F0IGltYWdlczoiLCBsZW5ndGgobGlzdC5maWxlcyh0cmFpbl9jYXRzX2RpcikpLCAiXG4iKQpjYXQoIiAtIHRvdGFsIHZhbGlkYXRpb24gY2F0IGltYWdlczoiLCBsZW5ndGgobGlzdC5maWxlcyh2YWxpZF9jYXRzX2RpcikpLCAiXG4iKQpjYXQoIiAtIHRvdGFsIHRlc3QgY2F0IGltYWdlczoiLCBsZW5ndGgobGlzdC5maWxlcyh0ZXN0X2NhdHNfZGlyKSksICJcblxuIikKCmNhdCgiRG9nIGltYWdlczoiLCAiXG4iKQpjYXQoIiAtIHRvdGFsIHRyYWluaW5nIGRvZyBpbWFnZXM6IiwgbGVuZ3RoKGxpc3QuZmlsZXModHJhaW5fZG9nc19kaXIpKSwgIlxuIikKY2F0KCIgLSB0b3RhbCB2YWxpZGF0aW9uIGRvZyBpbWFnZXM6IiwgbGVuZ3RoKGxpc3QuZmlsZXModmFsaWRfZG9nc19kaXIpKSwgIlxuIikKY2F0KCIgLSB0b3RhbCB0ZXN0IGRvZyBpbWFnZXM6IiwgbGVuZ3RoKGxpc3QuZmlsZXModGVzdF9kb2dzX2RpcikpLCAiXG4iKQpgYGAKCkxldCdzIGNoZWNrIG91dCB0aGUgZmlyc3QgMTAgY2F0IGFuZCBkb2cgaW1hZ2VzOgoKYGBge3IgZXhhbXBsZS1pbWFnZXN9Cm9wIDwtIHBhcihtZnJvdyA9IGMoNCwgNSksIHB0eSA9ICJzIiwgbWFyID0gYygwLjEsIDAuMSwgMC4xLCAwLjEpKQpmb3IgKGkgaW4gMToxMCkgewogIHBsb3QoYXMucmFzdGVyKGpwZWc6OnJlYWRKUEVHKHBhc3RlMCh0cmFpbl9jYXRzX2RpciwgIi9jYXQuIiwgaSwgIi5qcGciKSkpKQogIHBsb3QoYXMucmFzdGVyKGpwZWc6OnJlYWRKUEVHKHBhc3RlMCh0cmFpbl9kb2dzX2RpciwgIi9kb2cuIiwgaSwgIi5qcGciKSkpKQp9CnBhcihvcCkKYGBgCgojIFBhcnQgMjogTW9kZWwgMQoKIyMgRGVmaW5lIGFuZCBjb21waWxlIG1vZGVsCgpXZSdyZSBnb2luZyB0byBzZXQgdXAgYSBzaW1wbGUgQ05OIG1vZGVsIHRoYXQgY29udGFpbnMgc3RlcHMgeW91IHNhdyBpbiB0aGUgCnByZXZpb3VzIG1vZHVsZS4gVGhpcyBDTk4gaW5jbHVkZXM6CgotIEZvdXIgc2VxdWVudGlhbCBjb252IGFuZCBtYXggcG9vbGluZyBsYXllcnMKLSBGbGF0dGVuIGxheWVyCi0gRGVuc2x5LWNvbm5lY3RlZCBuZXR3b3JrCi0gU2luZ2xlIGJpbmFyeSBvdXRwdXQKCmBgYHtyIGNubi1hcmNoaXRlY3R1cmV9Cm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUKICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSAzMiwga2VybmVsX3NpemUgPSBjKDMsIDMpLCBhY3RpdmF0aW9uID0gInJlbHUiLCAKICAgICAgICAgICAgICAgIGlucHV0X3NoYXBlID0gYygxNTAsIDE1MCwgMykpICU+JQogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwgMikpICU+JQogIAogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDY0LCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JSAKICBsYXllcl9tYXhfcG9vbGluZ18yZChwb29sX3NpemUgPSBjKDIsIDIpKSAlPiUKICAKICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSAxMjgsIGtlcm5lbF9zaXplID0gYygzLCAzKSwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lIAogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwgMikpICU+JQogIAogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDEyOCwga2VybmVsX3NpemUgPSBjKDMsIDMpLCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUgCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLCAyKSkgJT4lCiAgCiAgbGF5ZXJfZmxhdHRlbigpICU+JQogIAogIGxheWVyX2RlbnNlKHVuaXRzID0gNTEyLCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGFjdGl2YXRpb24gPSAic2lnbW9pZCIpCgpzdW1tYXJ5KG1vZGVsKQpgYGAKCkNvbXBpbGUgdGhlIG1vZGVsOgoKYGBge3IgY25uLWNvbXBpbGV9Cm1vZGVsICU+JSBjb21waWxlKAogIGxvc3MgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSIsCiAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAxZS00KSwKICBtZXRyaWNzID0gImFjY3VyYWN5IgopCmBgYAoKIyMgUmVhZCBpbWFnZXMgZnJvbSBkaXJlY3RvcmllcwoKYGltYWdlX2RhdGFfZ2VuZXJhdG9yYCB3aWxsOgoKMS4gUmVhZCB0aGUgaW1hZ2UgZmlsZXMKMi4gRGVjb2RlIHRoZSBpbWFnZSB0byBSR0IgZ3JpZHMgb2YgcGl4ZWxzCjMuIENvbnZlcnQgdGhlc2UgaW50byBmbG9hdGluZyBwb2ludCB0ZW5zb3JzCjQuIFJlc2NhbGUgcGl4ZWwgdmFsdWVzIHRvIFswLCAxXSBpbnRlcnZhbAoKYGltYWdlX2RhdGFfZ2VuZXJhdG9yYCBwcm92aWRlcyBvdGhlciBjYXBhYmlsaXRpZXMgdGhhdCB3ZSdsbCBsb29rIGF0IHNob3J0bHkuCgpgZmxvd19pbWFnZXNfZnJvbV9kaXJlY3RvcnlgIHdpbGw6CgoxLiBBcHBseSBgaW1hZ2VfZGF0YV9nZW5lcmF0b3JgCjIuIFRvIGEgYmF0Y2ggb2YgMjAgaW1hZ2VzIGF0IGEgdGltZQozLiBGcm9tIG91ciB0cmFpbmluZyBkaXJlY3RvcnkgKHJhbmRvbWx5IHNodWZmbGluZyBidHduIHN1YmRpcmVjdG9yaWVzKQo0LiBSZXNpemUgdGhlc2UgaW1hZ2VzIHRvIGJlIGNvbnNpc3RlbnQgc2l6ZSBvZiAxNTB4MTUwIHBpeGVscwo1LiBBcHBseSBiaW5hcnkgbGFiZWxzCgpgYGB7ciBjbm4taW1hZ2UtZ2VuZXJhdG9yfQp0cmFpbl9kYXRhZ2VuIDwtIGltYWdlX2RhdGFfZ2VuZXJhdG9yKHJlc2NhbGUgPSAxLzI1NSkKdmFsaWRfZGF0YWdlbiA8LSBpbWFnZV9kYXRhX2dlbmVyYXRvcihyZXNjYWxlID0gMS8yNTUpCgp0cmFpbl9nZW5lcmF0b3IgPC0gZmxvd19pbWFnZXNfZnJvbV9kaXJlY3RvcnkoCiAgdHJhaW5fZGlyLAogIHRyYWluX2RhdGFnZW4sCiAgdGFyZ2V0X3NpemUgPSBjKDE1MCwgMTUwKSwKICBiYXRjaF9zaXplID0gMjAsCiAgY2xhc3NfbW9kZSA9ICJiaW5hcnkiCikKCnZhbGlkYXRpb25fZ2VuZXJhdG9yIDwtIGZsb3dfaW1hZ2VzX2Zyb21fZGlyZWN0b3J5KAogIHZhbGlkX2RpciwKICB2YWxpZF9kYXRhZ2VuLAogIHRhcmdldF9zaXplID0gYygxNTAsIDE1MCksCiAgYmF0Y2hfc2l6ZSA9IDIwLAogIGNsYXNzX21vZGUgPSAiYmluYXJ5IgopCmBgYAoKSWYgd2UgZ2V0IHRoZSBmaXJzdCBiYXRjaCBmcm9tIHRoZSBnZW5lcmF0b3IsIHlvdSB3aWxsIHNlZSB0aGF0IGl0IHlpZWxkcyAyMCAKaW1hZ2VzIG9mIDE1MHgxNTAgcGl4ZWxzIHdpdGggdGhyZWUgY2hhbm5lbHMgKDIwLCAxNTAsIDE1MCwgMykgYWxvbmcgd2l0aCB0aGVpciAKYmluYXJ5IGxhYmVscyAoMCwgMSkuCgpgYGB7ciBnZW5lcmF0b3Itc3RydWN0dXJlfQpiYXRjaCA8LSBnZW5lcmF0b3JfbmV4dCh0cmFpbl9nZW5lcmF0b3IpCnN0cihiYXRjaCkKYGBgCgojIyBUcmFpbiB0aGUgbW9kZWwKClRvIHRyYWluIG91ciBtb2RlbCB3ZSdsbCB1c2UgYGZpdF9nZW5lcmF0b3JgIHdoaWNoIGlzIHRoZSBlcXVpdmFsZW50IG9mIGBmaXRgIApmb3IgZGF0YSBnZW5lcmF0b3JzLiAgV2UgcHJvdmlkZSBpdCBvdXIgZ2VuZXJhdG9ycyBmb3IgdGhlIHRyYWluaW5nIGFuZCAKdmFsaWRhdGlvbiBkYXRhLiAgUGx1cywgd2UgbmVlZCB0byBzcGVjaWZ5OgoKLSBgc3RlcHNfcGVyX2Vwb2NoYDogaG93IG1hbnkgc2FtcGxlcyB0byBkcmF3IGZyb20gdGhlIHRyYWluaW5nIGdlbmVyYXRvciBiZWZvcmUgCiAgZGVjbGFyaW5nIGFuIGVwb2NoIG92ZXIuIE91ciBnZW5lcmF0b3Igc3VwcGxpZXMgYmF0Y2hlcyBvZiAyMCBhbmQgd2UgaGF2ZSAKICAyLDAwMCB0cmFpbmluZyBpbWFnZXMgc28gd2UgbmVlZCAxMDAgc3RlcHMuCi0gYHZhbGlkYXRpb25fc3RlcHNgOiBob3cgbWFueSBzYW1wbGVzIHRvIGRyYXcgZnJvbSB0aGUgdmFsaWRhdGlvbiBnZW5lcmF0b3IuIAogIE91ciBnZW5lcmF0b3Igc3VwcGxpZXMgYmF0Y2hlcyBvZiAyMCBhbmQgd2UgaGF2ZSAxLDAwMCB2YWxpZGF0aW9uIGltYWdlcyBzbyB3ZQogIG5lZWQgNTAgc3RlcHMuCiAgCioqV2l0aG91dCBhIEdQVSB0aGlzIHdpbGwgdGFrZSBhcHByb3hpbWF0ZWx5IDIwIG1pbnV0ZXMgdG8gdHJhaW4qKiAgCgpgYGB7ciBjbm4tdHJhaW59Cmhpc3RvcnkgPC0gbW9kZWwgJT4lIGZpdF9nZW5lcmF0b3IoCiAgdHJhaW5fZ2VuZXJhdG9yLAogIHN0ZXBzX3Blcl9lcG9jaCA9IDEwMCwKICBlcG9jaHMgPSAzMCwKICB2YWxpZGF0aW9uX2RhdGEgPSB2YWxpZGF0aW9uX2dlbmVyYXRvciwKICB2YWxpZGF0aW9uX3N0ZXBzID0gNTAKKQpgYGAKClZpZXcgaGlzdG9yeQoKYGBge3IgcGxvdC1oaXN0b3J5fQpwbG90KGhpc3RvcnkpCmBgYAoKIyBTYXZlIHRoZSBtb2RlbAoKYGBge3Igc2F2ZS1tb2RlbH0KbW9kZWwgJT4lIHNhdmVfbW9kZWxfaGRmNSgiY2F0c19hbmRfZG9nc19zbWFsbF8xLmg1IikKYGBgCgoKIyBQYXJ0IDM6IE1vZGVsIDIgd2l0aCBpbWFnZSBhdWdtZW50YXRpb24KCiMjIEltYWdlIEF1Z21lbnRhdGlvbgoKT3VyIG1vZGVsIGFib3ZlIGRvZXMgb2sgYnV0IGRlZmluaXRlbHkgaGFzIHJvb20gZm9yIGltcHJvdmVtZW50LiBPbmUgYXBwcm9hY2ggCnRvIGltcHJvdmUgcGVyZm9ybWFuY2UgaXMgdG8gY29sbGVjdCBtb3JlIGRhdGEuIFVuZm9ydHVuYXRlbHksIHRoaXMgaXMgbm90IGFsd2F5cyAKYW4gb3B0aW9uLiBBbiBhbHRlcm5hdGl2ZSBpcyB0byB1c2UgX19fZGF0YSBhdWdtZW50YXRpb25fX18uCgpgYGB7ciBpbWFnZS1hdWdtZW50YXRpb259CmRhdGFnZW4gPC0gaW1hZ2VfZGF0YV9nZW5lcmF0b3IoCiAgcmVzY2FsZSA9IDEvMjU1LAogIHJvdGF0aW9uX3JhbmdlID0gNDAsCiAgd2lkdGhfc2hpZnRfcmFuZ2UgPSAwLjIsCiAgaGVpZ2h0X3NoaWZ0X3JhbmdlID0gMC4yLAogIHNoZWFyX3JhbmdlID0gMC4yLAogIHpvb21fcmFuZ2UgPSAwLjIsCiAgaG9yaXpvbnRhbF9mbGlwID0gVFJVRSwKICBmaWxsX21vZGUgPSAibmVhcmVzdCIKKQpgYGAKClRoZSBmb2xsb3dpbmcgaGVscHMgdG8gdmlzdWFsaXplIHRoZSBpZGVhIG9mIGltYWdlIGF1Z21lbnRhdGlvbiBieToKCi0gUmVhZGluZyBpbiB0aGUgZmlyc3QgaW1hZ2UgYW5kIHJlc2l6aW5nIGl0IHRvIDE1MHgxNTAsCi0gQ29udmVydGluZyBpdCB0byBhbiBhcnJheSB3aXRoIHNoYXBlICgxNTAsIDE1MCwgMyksCi0gUmVzaGFwaW5nIGl0IHRvICgxLCAxNTAsIDE1MCwgMyksCi0gR2VuZXJhdGluZyBiYXRjaGVzIG9mIHJhbmRvbWx5IHRyYW5zZm9ybWVkIGltYWdlcy4KCmBgYHtyIHZpZXctYXVnbWVudGVkLWltYWdlc30KIyBnZXQgdGhlIGZpcnN0IGNhdCBpbWFnZQpmbmFtZXMgPC0gbGlzdC5maWxlcyh0cmFpbl9jYXRzX2RpciwgZnVsbC5uYW1lcyA9IFRSVUUpCmltZ19wYXRoIDwtIGZuYW1lc1tbMV1dCgojIHJlc2l6ZSAmIHJlc2hhcGUKaW1nIDwtIGltYWdlX2xvYWQoaW1nX3BhdGgsIHRhcmdldF9zaXplID0gYygxNTAsIDE1MCkpCmltZ19hcnJheSA8LSBpbWFnZV90b19hcnJheShpbWcpCmltZ19hcnJheSA8LSBhcnJheV9yZXNoYXBlKGltZ19hcnJheSwgYygxLCAxNTAsIDE1MCwgMykpCgojIGdlbmVyYXRlIGEgYSBzaW5nbGUgYXVnbWVudGVkIGltYWdlCmF1Z21lbnRhdGlvbl9nZW5lcmF0b3IgPC0gZmxvd19pbWFnZXNfZnJvbV9kYXRhKAogIGltZ19hcnJheSwKICBnZW5lcmF0b3IgPSBkYXRhZ2VuLAogIGJhdGNoX3NpemUgPSAxCikKCiMgcGxvdCAxMCBhdWdtZW50ZWQgaW1hZ2VzIG9mIHRoZSBmaXJzdCBjYXQgaW1hZ2UKb3AgPC0gcGFyKG1mcm93ID0gYygyLCA1KSwgcHR5ID0gInMiLCBtYXIgPSBjKDAsIDAuMSwgMCwgMC4xKSkKZm9yIChpIGluIDE6MTApIHsKICBiYXRjaCA8LSBnZW5lcmF0b3JfbmV4dChhdWdtZW50YXRpb25fZ2VuZXJhdG9yKQogIHBsb3QoYXMucmFzdGVyKGJhdGNoWzEsLCxdKSkKfQpwYXIob3ApCmBgYAoKIyMgTW9kZWwgMgoKTGV0J3MgY3JlYXRlIGEgbmV3IG1vZGVsIHRoYXQgaW5jbHVkZXMgaW1hZ2UgYXVnbWVudGF0aW9uIGFuZCB3ZSdsbCBhcHBseSB0aGUgCmRyb3BvdXQgcmVndWxhcml6YXRpb24gbWV0aG9kLiBUaGUgZm9sbG93aW5nIGNyZWF0ZXMgYSBDTk4gYXJjaGl0ZWN0dXJlIHdpdGg6CgotIEZvdXIgc2VxdWVudGlhbCBjb252IGFuZCBtYXggcG9vbGluZyBsYXllcnMKLSBGbGF0dGVuIGxheWVyCi0gRHJvcG91dCBsYXllciAobmV3KQotIERlbnNseS1jb25uZWN0ZWQgbmV0d29yawotIFNpbmdsZSBiaW5hcnkgb3V0cHV0CgpgYGB7ciBjbm4tc3RydWN0dXJlMn0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDMyLCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gYygxNTAsIDE1MCwgMykpICU+JQogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwgMikpICU+JQogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDY0LCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JSAKICBsYXllcl9tYXhfcG9vbGluZ18yZChwb29sX3NpemUgPSBjKDIsIDIpKSAlPiUKICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSAxMjgsIGtlcm5lbF9zaXplID0gYygzLCAzKSwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lIAogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwgMikpICU+JQogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDEyOCwga2VybmVsX3NpemUgPSBjKDMsIDMpLCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUgCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLCAyKSkgJT4lCiAgbGF5ZXJfZmxhdHRlbigpICU+JQogIGxheWVyX2Ryb3BvdXQocmF0ZSA9IDAuNSkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSA1MTIsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICJzaWdtb2lkIikKCm1vZGVsICU+JSBjb21waWxlKAogIGxvc3MgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSIsCiAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAxZS00KSwKICBtZXRyaWNzID0gImFjY3VyYWN5IgopCmBgYAoKTm93IHdlIGNhbiBhZGQgaW1hZ2UgYXVnbWVudGF0aW9uIHRvIG91ciBgaW1hZ2VfZGF0YV9nZW5lcmF0b3IoKWAuIFRoZSByZXN0IG9mIAp0aGUgaW5wdXRzIHJlbWFpbiB0aGUgc2FtZTsgaG93ZXZlciwgd2UgdXNlIGxhcmdlciBiYXRjaCBzaXplICgzMikgYW5kIHdlIGRvIG5vdCAKbmVlZCB0byB3b3JyeSBhYm91dCBgYmF0Y2hfc2l6ZWAgeCBgc3RlcHNfcGVyX2Vwb2NoYCBlcXVhbGluZyB0aGUgbnVtYmVyIG9mIAp0cmFpbmluZyBpbWFnZXMgc2luY2Ugd2UgYXJlIGRvaW5nIGltYWdlIGF1Z21lbnRhdGlvbi4KCmBgYHtyIGF1Z21lbnQtYW5kLXRyYWlufQojIG9ubHkgYXVnbWVudCB0cmFpbmluZyBkYXRhCnRyYWluX2RhdGFnZW4gPC0gaW1hZ2VfZGF0YV9nZW5lcmF0b3IoCiAgcmVzY2FsZSA9IDEvMjU1LAogIHJvdGF0aW9uX3JhbmdlID0gNDAsCiAgd2lkdGhfc2hpZnRfcmFuZ2UgPSAwLjIsCiAgaGVpZ2h0X3NoaWZ0X3JhbmdlID0gMC4yLAogIHNoZWFyX3JhbmdlID0gMC4yLAogIHpvb21fcmFuZ2UgPSAwLjIsCiAgaG9yaXpvbnRhbF9mbGlwID0gVFJVRSwKKQoKIyBkbyBub3QgYXVnbWVudCB0ZXN0IGFuZCB2YWxpZGF0aW9uIGRhdGEKdGVzdF9kYXRhZ2VuIDwtIGltYWdlX2RhdGFfZ2VuZXJhdG9yKHJlc2NhbGUgPSAxLzI1NSkKCiMgZ2VuZXJhdGUgYmF0Y2hlcyBvZiBkYXRhIGZyb20gdHJhaW5pbmcgZGlyZWN0b3J5CnRyYWluX2dlbmVyYXRvciA8LSBmbG93X2ltYWdlc19mcm9tX2RpcmVjdG9yeSgKICB0cmFpbl9kaXIsCiAgdHJhaW5fZGF0YWdlbiwKICB0YXJnZXRfc2l6ZSA9IGMoMTUwLCAxNTApLAogIGJhdGNoX3NpemUgPSAzMiwKICBjbGFzc19tb2RlID0gImJpbmFyeSIKKQoKIyBnZW5lcmF0ZSBiYXRjaGVzIG9mIGRhdGEgZnJvbSB2YWxpZGF0aW9uIGRpcmVjdG9yeQp2YWxpZGF0aW9uX2dlbmVyYXRvciA8LSBmbG93X2ltYWdlc19mcm9tX2RpcmVjdG9yeSgKICB2YWxpZF9kaXIsCiAgdGVzdF9kYXRhZ2VuLAogIHRhcmdldF9zaXplID0gYygxNTAsIDE1MCksCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIGNsYXNzX21vZGUgPSAiYmluYXJ5IgopCgojIHRyYWluIG1vZGVsCmhpc3RvcnkgPC0gbW9kZWwgJT4lCiAgZml0X2dlbmVyYXRvcigKICAgIHRyYWluX2dlbmVyYXRvciwKICAgIHN0ZXBzX3Blcl9lcG9jaCA9IDEwMCwKICAgIGVwb2NocyA9IDEwMCwKICAgIHZhbGlkYXRpb25fZGF0YSA9IHZhbGlkYXRpb25fZ2VuZXJhdG9yLAogICAgdmFsaWRhdGlvbl9zdGVwcyA9IDUwCiAgKQpgYGAKCmBgYHtyIHNhdmUtYXVnbWVudGVkLW1vZGVsfQptb2RlbCAlPiUgc2F2ZV9tb2RlbF9oZGY1KCJjYXRzX2FuZF9kb2dzX3NtYWxsXzIuaDUiKQpgYGAKCg==